package IC.lir;

import IC.AST.*;
import IC.AST.PrimitiveType;
import IC.Symbols.*;
import IC.Types.*;
import IC.BinaryOps;
import IC.LiteralTypes;
import IC.UnaryOps;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class LIRTranslationVisitor implements Visitor {

	private int regUID = 0;
	private int UID = 0;
	private String labelBreak = null;
	private String labelContinue = null;
	private SymbolTable symbolTable;
	private TypeTable typeTable;
	private List<String> literalImplList = new LinkedList<String>();
	private List<String> dispatchTList = new LinkedList<String>();
	private Map<String, String> literalMap = new LinkedHashMap<String, String>();

	public LIRTranslationVisitor(SymbolTable globalSymbolTable, TypeTable typeTable) {
		symbolTable = globalSymbolTable;
		this.typeTable = typeTable;
	}

	public void AssignLirTitles(SymbolTable symbolTable) {
		if (symbolTable.seenByLir())
			return;
		else
			symbolTable.setSeenByLir();

		// act on overridden method
		ASTNode method = symbolTable.getAstNode();
		if (method instanceof Method)
			if (((Method)method).getOverriddenMethod()!=null)
				AssignLirTitles(((Method)method).getOverriddenMethod().getMethodSymbolTable());

		// set LIR name for every symbol
		for (Symbol symbol : symbolTable.getSymbolsMap().values()) {
			if (symbol.getKind() == Kind.Variable)
				symbol.setLirTitle("v" + (++UID) + symbol.getName());

			if (symbol.getKind() == Kind.Parameter) {
				Formal formal = (Formal)symbol.getAstNode();
				if (formal.getOverriddenFormal()!=null)
					symbol.setLirTitle(formal.getOverriddenFormal().getSymbolTableEntry().getLirTitle());
				else
					symbol.setLirTitle("p" + (++UID) + symbol.getName());
			}
		}
		// act on sub-tables
		for (SymbolTable childSymbolTable : symbolTable.getSubTables())
			AssignLirTitles(childSymbolTable);
	}

	public Object visit(Program program) {
		List<String> progTranslationList = new LinkedList<String>();

		// set LIR name for variables
		AssignLirTitles(symbolTable);

		AssignTitlesForMethods(program);

		List<String> classTranslationList = LirClassTranslator(program,
				progTranslationList);

		// add error alerts
		AddErrors();

		AddProlog(progTranslationList, classTranslationList);

		// set LIR entry point and error handling
		Method main = program.getMain();

		AddTranslatorConsts(progTranslationList, main);

		return progTranslationList;
	}
	
	private List<String> LirClassTranslator(Program program,
			List<String> progTranslationList) {
		List<String> classTranslationList = new LinkedList<String>();
		for (ICClass icClass : program.getClasses())
			if (!icClass.getName().equals("Library")) {
				classTranslationList.add("");
				classTranslationList.add("# Class Code for '" +icClass.getName()+ "'");
				classTranslationList.addAll((List<String>)icClass.accept(this));
			}
		for (ICClass icClass : program.getClasses())
			if (!icClass.getName().equals("Library")) {
				progTranslationList.add("# Class "+icClass.getName()+" Offsets:");
				for (Field fld : icClass.getFields())
					progTranslationList.add("# " + fld.getSymbolTableEntry().getSymbolDistance() + ": " + fld.getName());
				progTranslationList.add("");
			}
		return classTranslationList;
	}

	private void AssignTitlesForMethods(Program program) {
		// set LIR name for methods
		for (ICClass icClass : program.getClasses())
			for (Symbol symbol : icClass.getClassSymbolTable().getSymbolsMap().values())
				if (symbol.getKind() ==  Kind.VirtualMethod || symbol.getKind() == Kind.StaticMethod) 
					symbol.setLirTitle(((symbol.getAstNode() instanceof LibraryMethod)? "_" : ("_"+icClass.getName())) +"_"+symbol.getName());
	}

	public Object visit(ICClass icClass) {

		List<String> classTranslationList = new LinkedList<String>();
		SymbolTable classSymbolTable = icClass.getClassSymbolTable();
		String[] dispatchTableArray = new String[classSymbolTable.getNumberOfItemsInDispatchTable()];

		while (classSymbolTable!=null && classSymbolTable.getSymbolTableType()==SymbolTableType.Class) {
			System.out.println(classSymbolTable);
			for (Symbol symbol : classSymbolTable.getSymbolsMap().values()){
				System.out.println(symbol);
				if (symbol.getKind()==Kind.VirtualMethod){
					if (symbol.getSymbolDistance() >= dispatchTableArray.length){
						System.out.println("baa - " + icClass.getName());
					}
					//check method was not overridden
					if (symbol.getSymbolDistance() < dispatchTableArray.length && dispatchTableArray[symbol.getSymbolDistance()]==null)
						dispatchTableArray[symbol.getSymbolDistance()] = symbol.getLirTitle();
				}
			}
			classSymbolTable = classSymbolTable.getParentSymbolTable();
		}

		// build dispatch-table statement
		StringBuilder dispatchTStr = new StringBuilder();
		dispatchTStr.append("_DV_").append(icClass.getName()).append(": [");
		for (String mtdName : dispatchTableArray)
			dispatchTStr.append(mtdName).append(",");
		if (dispatchTableArray.length > 0)
			dispatchTStr.deleteCharAt(dispatchTStr.length()-1);
		dispatchTStr.append("]");
		dispatchTList.add(dispatchTStr.toString());

		//add all methods
		for (Method mtd : icClass.getMethods()) {
			classTranslationList.add("");
			classTranslationList.add("# Method Code for '" + mtd.getName() + "'");
			classTranslationList.addAll((List<String>)mtd.accept(this));
		}

		return classTranslationList;
	}

	private List<String> methodHelper(Method method) {
		List<String> methodTr = new LinkedList<String>();

		methodTr.add(method.getSymbolTableEntry().getLirTitle()+":");

		//add all statements
		for (Statement st : method.getStatements())
			methodTr.addAll((List<String>)st.accept(this));

		//for void methods and for non-void methods that do not return on all code paths (got a warning in context analysis).
		methodTr.add("Return 9999");

		return methodTr;
	}

	public Object visit(Assignment assignment) {
		List<String> assignmentTranslationList = new LinkedList<String>();

		int targetRegistedID = regUID;
		assignmentTranslationList.addAll((List<String>)assignment.getAssignment().accept(this));
		regUID = targetRegistedID+1;
		
		Location location = assignment.getVariable();
		if (location instanceof ArrayLocation) {

			int arrExprTarget = regUID;
			assignmentTranslationList.addAll((List<String>)((ArrayLocation) location).getArray().accept(this));
			regUID = arrExprTarget+1;
			int arrIndxTarget = regUID;
			assignmentTranslationList.addAll((List<String>)((ArrayLocation) location).getIndex().accept(this));
			regUID = arrIndxTarget+1;

			assignmentTranslationList.add("Compare 0,R" + arrExprTarget);
			assignmentTranslationList.add("JumpTrue _pointer_is_null_reference_error");
			assignmentTranslationList.add("Compare 0,R" + arrIndxTarget);
			assignmentTranslationList.add("JumpL _array_index_out_of_bounds_error");

			int sizeTarget = regUID;
			assignmentTranslationList.add("ArrayLength R" + arrExprTarget + ",R" + sizeTarget);
			regUID = sizeTarget+1;

			assignmentTranslationList.add("Compare R" + sizeTarget +",R" + arrIndxTarget);
			assignmentTranslationList.add("JumpGE _array_index_out_of_bounds_error");

			assignmentTranslationList.add("MoveArray R" + targetRegistedID + ",R" + arrExprTarget + "[R" + arrIndxTarget + "]");
		} else if (location instanceof VariableLocation) {
			VariableLocation variableLocation = (VariableLocation)location;
			Symbol symbol = variableLocation.getSymbolTableEntry();

			if (symbol.getKind()==Kind.Field) {
				int instanceTarget = regUID;
				if (variableLocation.isExternal())
					assignmentTranslationList.addAll((List<String>)variableLocation.getLocation().accept(this));
				else
					assignmentTranslationList.add("Move this,R" + instanceTarget);
				regUID = instanceTarget + 1;
				
				assignmentTranslationList.add("Compare 0,R" + instanceTarget);
				assignmentTranslationList.add("JumpTrue _pointer_is_null_reference_error");
				assignmentTranslationList.add("MoveField R" + targetRegistedID + ",R" + instanceTarget + "." + symbol.getSymbolDistance());
			} else {
				assignmentTranslationList.add("Move R" + targetRegistedID + "," + symbol.getLirTitle());
			}
		}

		regUID = targetRegistedID;
		return assignmentTranslationList;
	}
	
	private void ConditionElseHandler(If ifStatement,
			List<String> ifTranslationList, String labelEnd, String labelFalse,
			int targetRegisterForCondition) {
		if (ifStatement.hasElse()) {
			ifTranslationList.add("Jump "+labelEnd);
			ifTranslationList.add(labelFalse+":");
			List<String> elseOp = (List<String>)ifStatement.getElseOperation().accept(this);
			regUID = targetRegisterForCondition + 1;
			ifTranslationList.addAll(elseOp);
		}
	}
	
	private void AddCompForArrayCreation(List<String> newArrayTranslationList,
			int sizeTarget, int regtarget) {
		newArrayTranslationList.add("Compare 0,R" + sizeTarget);
		/*jumps to the error label if the array size is negative*/
		newArrayTranslationList.add("JumpL _array_negative_size_error");
		newArrayTranslationList.add("Mul 4,R" + sizeTarget);
		newArrayTranslationList.add("Library __allocateArray(R" +sizeTarget +"),R" +regtarget);
	}

	public Object visit(Return returnStatement) {
		List<String> returnTranslationList = new LinkedList<String>();

		if (returnStatement.hasValue()) {
			int returnTargetRegister = regUID;
			List<String> returnExpressionList = (List<String>)returnStatement.getValue().accept(this);
			regUID = returnTargetRegister + 1;
			
			returnTranslationList.addAll(returnExpressionList);
			returnTranslationList.add("Return R" + returnTargetRegister);

			regUID = returnTargetRegister;
		} else 
			returnTranslationList.add("Return Rdummy");

		return returnTranslationList;
	}

	public Object visit(If ifStatement) {
		List<String> ifTranslationList = new LinkedList<String>();
		String labelEnd = "_if_end_label" + (++UID);
		String labelFalse = labelEnd;
		if (ifStatement.hasElse())
			labelFalse = "_if_else_label" + (++UID);

		int targetRegisterForCondition = regUID;
		List<String> cond = (List<String>)ifStatement.getCondition().accept(this);
		regUID = targetRegisterForCondition+1;
		ifTranslationList.addAll(cond);

		ifTranslationList.add("Compare 0,R"+targetRegisterForCondition);
		ifTranslationList.add("JumpTrue "+labelFalse);

		List<String> op = (List<String>)ifStatement.getOperation().accept(this);
		regUID = targetRegisterForCondition+1;
		ifTranslationList.addAll(op);

		ConditionElseHandler(ifStatement, ifTranslationList, labelEnd,
				labelFalse, targetRegisterForCondition);

		ifTranslationList.add(labelEnd+":");
		regUID = targetRegisterForCondition;
		return ifTranslationList;
	}

	public Object visit(While whileStatement) {
		List<String> whileTranslationList = new LinkedList<String>();

		String labelTest = "_while_test_label" + (++UID);
		String labelEnd = "_while_end_label" + (++UID);
		whileTranslationList.add(labelTest + ":");

		int targetRegisterForCondition = regUID;
		List<String> cond = (List<String>)whileStatement.getCondition().accept(this);
		regUID = targetRegisterForCondition + 1;

		whileTranslationList.addAll(cond);
		whileTranslationList.add("Compare 0,R"+targetRegisterForCondition);
		whileTranslationList.add("JumpTrue "+labelEnd);

		String breakOld = labelBreak;
		String continueOld = labelContinue;

		labelBreak = labelEnd;
		labelContinue = labelTest;
		List<String> op = (List<String>)whileStatement.getOperation().accept(this);
		regUID = targetRegisterForCondition + 1;

		labelBreak = breakOld;
		labelContinue = continueOld;

		whileTranslationList.addAll(op);
		whileTranslationList.add("Jump " + labelTest);
		whileTranslationList.add(labelEnd + ":");

		regUID = targetRegisterForCondition;

		return whileTranslationList;
	}
	
	private void FieldHandler(VariableLocation variableLocation, Symbol symbol,
			List<String> varLocationTr, int targetRegisterForVariable) {
		int instanceRegTarget = regUID;
		if (variableLocation.isExternal()) {
			List<String> externalTr = (List<String>)variableLocation.getLocation().accept(this);
			varLocationTr.addAll(externalTr);
		} else {
			varLocationTr.add("Move this,R" + instanceRegTarget);
		}
		varLocationTr.add("Compare 0,R" + instanceRegTarget);
		varLocationTr.add("JumpTrue _pointer_is_null_reference_error");
		varLocationTr.add("MoveField R" + instanceRegTarget + "."+symbol.getSymbolDistance()+",R" +targetRegisterForVariable);
	}

	public Object visit(Break breakStatement) {
		List<String> breakTr = new LinkedList<String>();
		breakTr.add("Jump " + this.labelBreak);
		return breakTr;
	}

	public Object visit(Continue continueStatement) {
		List<String> continueTr = new LinkedList<String>();
		continueTr.add("Jump " + this.labelContinue);
		return continueTr;
	}

	public Object visit(StatementsBlock statementsBlock) {
		List<String> blockTr = new LinkedList<String>();
		for (Statement s : statementsBlock.getStatements()) {
			List<String> sTr = (List<String>)s.accept(this);
			blockTr.addAll(sTr);
		}
		return blockTr;
	}
	
	private void ArrayInderHandler(ArrayLocation location,
			List<String> locationTranslationList, int arrayIndexTarget) {
		locationTranslationList.addAll((List<String>)((ArrayLocation) location).getIndex().accept(this));
		regUID = arrayIndexTarget+1;
	}

	public Object visit(LocalVariable localVariable) {
		List<String> varTr = new LinkedList<String>();

		if (localVariable.hasInitValue()) {
			int initValueRegTarget = regUID;
			Symbol varSym = localVariable.getSymbolTableEntry();
			List<String> initValueTr = (List<String>)localVariable.getInitValue().accept(this);
			regUID = initValueRegTarget + 1;
			varTr.addAll(initValueTr);
			varTr.add("Move R" + initValueRegTarget + "," + varSym.getLirTitle());
			regUID = initValueRegTarget;
		}
		return varTr;
	}

	public Object visit(VariableLocation variableLocation) {
		Symbol symbol = variableLocation.getSymbolTableEntry();
		List<String> varLocationTr = new LinkedList<String>();
		int targetRegisterForVariable = regUID;
		regUID = targetRegisterForVariable + 1;

		PerKindVariableHandler(variableLocation, symbol, varLocationTr,
				targetRegisterForVariable);
		regUID = targetRegisterForVariable;
		return varLocationTr;
	}
	
	private void NonLibraryMethodCallBuilder(Call call, Method method,
			String targetReg, int initialParamTarget, StringBuilder callBuilder) {
		callBuilder.append("(");
		String seperate = "";
		List<Formal> formals = method.getFormals();
		for (int i = 0; i < call.getArguments().size(); i++) {
			callBuilder.append(seperate);
			callBuilder.append(formals.get(i).getSymbolTableEntry().getLirTitle());
			callBuilder.append("=R").append(initialParamTarget + i);
			seperate = ",";
		}
		callBuilder.append("),").append(targetReg);
	}

	public Object visit(ArrayLocation location) {
		int regTarget = regUID;
		regUID++;

		int arrayExpressionTarget = regUID;
		List<String> locationTranslationList = ArrayLocationHandler(location,regUID);
		int arrayIndexTarget = regUID;
		ArrayInderHandler(location, locationTranslationList, regUID);

		WriteArrayLocationLir(locationTranslationList, regTarget, arrayExpressionTarget,
				arrayIndexTarget);
		regUID = regTarget;
		return locationTranslationList;
	}
	private void NonLibraryMethodHandler(Call call,
			Expression externalExpression, List<String> callTranslationList,
			Symbol symbol, Method method, String targetReg,
			int initialParamTarget, StringBuilder callBuilder) {
		if (method instanceof StaticMethod) {
			callBuilder.append("StaticCall ").append(symbol.getLirTitle());
		} 
		else if (method instanceof VirtualMethod) {
			VirtualMethodHandler(externalExpression, callTranslationList,
					symbol, callBuilder);
		}
		NonLibraryMethodCallBuilder(call, method, targetReg,
				initialParamTarget, callBuilder);
	}

	private List<String> visitCall(Call call, Expression externalExpression) {
		List<String> callTranslationList = new LinkedList<String>();
		Symbol symbol = call.getSymbolTableEntry();
		Method method = (Method)symbol.getAstNode();
		MethodType methodType = (MethodType)symbol.getType();
		int returnTarget = regUID;
		String targetReg = "Rdummy";
		if (methodType.getMethodReturnType() != typeTable.getVoidPrimitiveType()) {
			targetReg = "R"+returnTarget;
		}

		/*translate the parameters*/
		int initialParamTarget = regUID;
		int currentParamTarget = regUID;
		currentParamTarget = ArgumentsWalker(call, callTranslationList,
				currentParamTarget);
		
		StringBuilder callBuilder = new StringBuilder();
		/*check if it's a library method*/
		if (method instanceof LibraryMethod) {
			LibraryMethodHandler(call, symbol, targetReg, initialParamTarget,
					callBuilder);
		} 
		else {
			NonLibraryMethodHandler(call, externalExpression,
					callTranslationList, symbol, method, targetReg,
					initialParamTarget, callBuilder);
		}

		callTranslationList.add(callBuilder.toString());
		regUID = returnTarget;
		return callTranslationList;
	}

	private void VirtualMethodHandler(Expression externalExpression,
			List<String> callTranslationList, Symbol symbol,
			StringBuilder callBuilder) {
		int instanceTarget = regUID;
		if (externalExpression == null) {
			callTranslationList.add("Move this,R" + instanceTarget);
		} 
		else {
			/*translate the external expression*/
			List<String> externalTr = (List<String>)externalExpression.accept(this);
			callTranslationList.addAll(externalTr);
		}
		regUID = instanceTarget+1;
		callTranslationList.add("Compare 0,R" + instanceTarget);
		callTranslationList.add("JumpTrue _pointer_is_null_reference_error");
		callBuilder.append("VirtualCall R").append(instanceTarget).append(".").append(symbol.getSymbolDistance());
	}

	private void LibraryMethodHandler(Call call, Symbol symbol,
			String targetReg, int initialParamTarget, StringBuilder callBuilder) {
		callBuilder.append("Library ").append(symbol.getLirTitle()).append("(");
		String seperate = "";
		for (int i = 0; i < call.getArguments().size(); i++) {
			callBuilder.append(seperate);
			callBuilder.append("R").append(initialParamTarget + i);
			seperate = ",";
		}
		callBuilder.append("),").append(targetReg);
	}

	public Object visit(VirtualCall call) {
		return visitCall(call, call.isExternal() ? call.getLocation() : null);
	}

	public Object visit(This thisExpression) {
		return Arrays.asList("Move this,R" + regUID);
	}

	public Object visit(NewClass newClass) {
		return Arrays.asList("Library __allocateObject(" +((ClassType)newClass.getSemanticType()).getIcClass().getClassSymbolTable().getNumberOfItemsInInstanceTable() * 4 +"),R" +regUID,"MoveField _DV_" + newClass.getName() +",R" +regUID +".0");
	}

	public Object visit(NewArray newArray) {
		List<String> newArrayTr = new LinkedList<String>();
		int sizeTarget = regUID;
		int regtarget = regUID;
		
		/*translate the size*/
		List<String> sizeTr = (List<String>)newArray.getSize().accept(this);
		regUID = sizeTarget+1;
		newArrayTr.addAll(sizeTr);

		AddCompForArrayCreation(newArrayTr, sizeTarget, regtarget);

		regUID = regtarget;
		return newArrayTr;
	}
	

	private void AddProlog(List<String> progTranslationList,
			List<String> classTranslationList) {
		progTranslationList.add("# String Literals");
		progTranslationList.addAll(literalImplList);
		progTranslationList.add("");
		progTranslationList.add("# Dispatch Tables");
		progTranslationList.addAll(dispatchTList);
		progTranslationList.addAll(classTranslationList);
	}

	public Object visit(Length length) {
		List<String> lengthTr = new LinkedList<String>();

		int arrayTarget = regUID;
		int regTarget = regUID;

		/*translate the array*/
		List<String> arrayTr = (List<String>)length.getArray().accept(this);
		lengthTr.addAll(arrayTr);
		
		regUID = arrayTarget+1;
		lengthTr.add("Compare 0,R" + arrayTarget);
		/*jump to error label if array is null*/
		lengthTr.add("JumpTrue _pointer_is_null_reference_error");
		lengthTr.add("ArrayLength R" + arrayTarget +",R"+ regTarget);

		regUID = regTarget;
		return lengthTr;
	}
	
	private void AddTranslatorConsts(List<String> progTranslationList,
			Method main) {
		progTranslationList.add("");
		progTranslationList.add("");
		progTranslationList.add("# start the program");
		progTranslationList.add("_ic_main:");
		progTranslationList.add("StaticCall "+main.getSymbolTableEntry().getLirTitle()+
				"("+main.getFormals().get(0).getSymbolTableEntry().getLirTitle()+"=0),Rdummy");
		progTranslationList.add("Jump _exit_program");

		progTranslationList.add("");
		progTranslationList.add("");
		progTranslationList.add("# Error labels");
		progTranslationList.add("_pointer_is_null_reference_error:");
		progTranslationList.add("Library __println(str_pointer_is_null_reference_error),Rdummy");
		progTranslationList.add("Jump _exit_program");
		progTranslationList.add("");
		progTranslationList.add("_divided_with_zero_error:");
		progTranslationList.add("Library __println(str_divided_with_zero_error),Rdummy");
		progTranslationList.add("Jump _exit_program");
		progTranslationList.add("");
		progTranslationList.add("_array_index_out_of_bounds_error:");
		progTranslationList.add("Library __println(str_array_index_out_of_bounds_error),Rdummy");
		progTranslationList.add("Jump _exit_program");
		progTranslationList.add("");
		progTranslationList.add("_array_negative_size_error:");
		progTranslationList.add("Library __println(str_array_negative_size_error),Rdummy");
		progTranslationList.add("Jump _exit_program");
		progTranslationList.add("");
		progTranslationList.add("");
		progTranslationList.add("# exit the program");
		progTranslationList.add("_exit_program:");
	}
	
	private void AddErrors() {
		literalImplList.add("str_divided_with_zero_error: \"Runtime Error: Divided with zero\"");
		literalImplList.add("str_pointer_is_null_reference_error: \"Runtime Error: Null pointer dereference\"");
		literalImplList.add("str_array_index_out_of_bounds_error: \"Runtime Error: Array index out of bounds\"");
		literalImplList.add("str_array_negative_size_error: \"Runtime Error: Array allocated with a negative size\"");
	}
	

	public Object visit(MathBinaryOp binaryOp) {
		List<String> binaryOpTr = new LinkedList<String>();
		int regTarget = regUID;

		/*translate the first operand*/
		int firstOperandTarget = regTarget;
		List<String> firstOperandTr = (List<String>)binaryOp.getFirstOperand().accept(this);
		binaryOpTr.addAll(firstOperandTr);
		
		/*translate the second operand*/
		regUID = regTarget+1;
		int secondOperandTarget = regUID;
		List<String> secondOperandTr = (List<String>)binaryOp.getSecondOperand().accept(this);
		regUID = secondOperandTarget + 1;
		binaryOpTr.addAll(secondOperandTr);

		switch (binaryOp.getOperator()) {
		case MINUS:
			binaryOpTr.add("Sub R" + secondOperandTarget + ",R" + firstOperandTarget);
			break;
		case PLUS:
			/*if the first operand is a string then the '+' operator means to concatenate*/ 
			if ( (this.typeTable.getStringPrimitiveType()) == (binaryOp.getFirstOperand().getSemanticType()) ) {
				binaryOpTr.add("Library __stringCat(R" + firstOperandTarget + ",R" + secondOperandTarget + "),R" + regTarget);
			} 
			/*the first operand is not a string*/
			else {
				binaryOpTr.add("Add R" + secondOperandTarget + ",R" + firstOperandTarget);
			}
			break;
		case DIVIDE:
			binaryOpTr.add("Compare 0,R" + secondOperandTarget);
			/*jump to the error label if the second operand equals to zero*/
			binaryOpTr.add("JumpTrue _divided_with_zero_error");
			binaryOpTr.add("Div R" +secondOperandTarget+ ",R" +firstOperandTarget);
			break;
		/*jump to the error label if the second oeprand equals to zero*/
		case MOD:
			binaryOpTr.add("Compare 0,R" + secondOperandTarget);
			binaryOpTr.add("JumpTrue _divided_with_zero_error");
			binaryOpTr.add("Mod R" + secondOperandTarget+ ",R"+ firstOperandTarget);
			break;
		case MULTIPLY:
			binaryOpTr.add("Mul R" + secondOperandTarget+ ",R"+ firstOperandTarget);
			break;
		}
		regUID = regTarget;
		return binaryOpTr;
	}

	public Object visit(LogicalBinaryOp binaryOp) {
		List<String> binaryOpTr = new LinkedList<String>();
		int regTarget = regUID;

		List<String> firstOperandTr = (List<String>)binaryOp.getFirstOperand().accept(this);
		regUID = regTarget+1;
		binaryOpTr.addAll(firstOperandTr);
		UID++;
		String endLabel = "_bin_operation_end_label" + UID;
		/*check is the operator is && (and)*/
		if (binaryOp.getOperator() == BinaryOps.LAND) {
			/*jumps only if the first operand is false*/
			binaryOpTr.add("Compare 0,R" + regTarget);
			binaryOpTr.add("JumpTrue " + endLabel);
			
		} else if (binaryOp.getOperator() == BinaryOps.LOR) {
			/*jumps to the label if the first operand is true*/
			binaryOpTr.add("Compare 1,R" + regTarget);
			binaryOpTr.add("JumpTrue " + endLabel);
		}
		
		int secondOperandTarget = regUID;
		List<String> secondOperandTr = (List<String>)binaryOp.getSecondOperand().accept(this);
		regUID = secondOperandTarget+ 1;
		binaryOpTr.addAll(secondOperandTr);
		
		switch (binaryOp.getOperator()) {
		case LOR:
			binaryOpTr.add("Or R"+ secondOperandTarget +",R" + regTarget);
			break;
		case LAND:
			binaryOpTr.add("And R"+ secondOperandTarget +",R" + regTarget);
			break;
		default:
			int operand1Target = regUID;
			binaryOpTr.add("Move R" + regTarget+ ",R" +operand1Target);
			regUID = operand1Target + 1;
			binaryOpTr.add("Move 1,R" +regTarget);
			binaryOpTr.add("Compare R"+ secondOperandTarget+ ",R" + operand1Target);
			/*check the operator*/
			switch (binaryOp.getOperator()) {
			/*jumps if first operand is less than or equal second operand*/
			case LTE:
				binaryOpTr.add("JumpLE " + endLabel);
				break;
			/*jumps to the end label if first operand is less than the second operand*/
			case LT:
				binaryOpTr.add("JumpL " + endLabel);
				break;
			/*jumps if first operand is greater or equal second operand*/
			case GTE:
				binaryOpTr.add("JumpGE " + endLabel);
				break;
			/*jumps if the first operand is greater than the second operand*/
			case GT:
				binaryOpTr.add("JumpG " + endLabel);
				break;

			/*if the operand are not equal*/
			case NEQUAL:
				binaryOpTr.add("JumpFalse " + endLabel);
				break;
			/*if the operands are equal*/
			case EQUAL:
				binaryOpTr.add("JumpTrue " + endLabel);
				break;
			}
			binaryOpTr.add("Move 0,R" + regTarget);
			break;
		}
		binaryOpTr.add(endLabel + ":");
		regUID = regTarget;
		return binaryOpTr;
	}
	
	private List<String> ArrayLocationHandler(ArrayLocation location,
			int arrayExpTarget) {
		List<String> locationTranslationList = new LinkedList<String>();
		locationTranslationList.addAll((List<String>)((ArrayLocation) location).getArray().accept(this));
		regUID = arrayExpTarget + 1;
		return locationTranslationList;
	}
	

	public Object visit(MathUnaryOp unaryOp) {
		List<String> unaryOpTr = new LinkedList<String>();
		int regTarget = regUID;	
		/*checks if the unary operation is unary subtraction */
		if (unaryOp.getOperator() == UnaryOps.UMINUS) {
			if (unaryOp.getOperand() instanceof  Literal) {
				Literal literal = (Literal)unaryOp.getOperand();
				if ( (literal.getType() == LiteralTypes.INTEGER) && ((Long)literal.getValue()) == 0x80000000L) {
					unaryOpTr.add("Move " + 0x80000000L + ",R" + regTarget);
					unaryOpTr.add("Neg R" + regTarget);
					regUID = regTarget;
					return unaryOpTr;
				}
			}
			List<String> operandTr = (List<String>)unaryOp.getOperand().accept(this);
			unaryOpTr.addAll(operandTr);
			/*negate the register*/
			unaryOpTr.add("Neg R" + regTarget);
		}
		regUID = regTarget;
		return unaryOpTr;
	}

	public Object visit(LogicalUnaryOp unaryOp) {
		List<String> unaryOpTr = new LinkedList<String>();

		int regTarget = regUID;
		/*check if the unaryOp is logical negation*/
		if (unaryOp.getOperator() == UnaryOps.LNEG) {
			List<String> operTr = (List<String>)unaryOp.getOperand().accept(this);
			unaryOpTr.addAll(operTr);
			
			/*The xor implements the logical negation - if R = 1 then the result is 0 and if R = 0  then the result is 1*/ 
			unaryOpTr.add("Xor 1,R" + regTarget);
		}
		regUID = regTarget;
		return unaryOpTr;
	}

	public Object visit(Literal literal) {
		List<String> literalTr = new LinkedList<String>();
		LiteralTypes type = literal.getType();
		
		/*check the literal type*/
		switch (type) {
		case STRING:
			String literalLabel;
			String strValue = literal.getValue().toString();
			if (this.literalMap.containsKey(strValue)) {
				literalLabel = this.literalMap.get(strValue);
			} 
			else {
				UID++;
				literalLabel = "str"+ UID;
				this.literalImplList.add(literalLabel + ": " + strValue);
				this.literalMap.put(strValue, literalLabel);
			}
			literalTr.add("Move "+ literalLabel +",R" +regUID);
			break;
			
		case TRUE:
			literalTr.add("Move 1,R"+ regUID);
			break;
			
		case INTEGER:
			literalTr.add("Move " +literal.getValue() +",R" + regUID);
			break;
			
		case FALSE:
		case NULL:
			literalTr.add("Move 0,R"+ regUID);
			break;
		}
		return literalTr;
	}
	
	private void WriteArrayLocationLir(List<String> locationTr, int regTarget,
			int arrayExpTarget, int arrayIndexTarget) {
		locationTr.add("Compare 0,R" + arrayExpTarget);
		locationTr.add("JumpTrue _pointer_is_null_reference_error");
		/*jump to error label if the array index is out of bounds*/
		locationTr.add("Compare 0,R" + arrayIndexTarget);
		locationTr.add("JumpL _array_index_out_of_bounds_error");

		int regLength = regUID;
		locationTr.add("ArrayLength R" + arrayExpTarget + ",R" + regLength);
		regUID = regLength+1;
		locationTr.add("Compare R" + regLength +",R" + arrayIndexTarget);
		/*jump if the index is greater or equal to the array length*/
		locationTr.add("JumpGE _array_index_out_of_bounds_error");
		locationTr.add("MoveArray R" + arrayExpTarget + "[R" + arrayIndexTarget + "], R" + regTarget);
	}


	public Object visit(ExpressionBlock expressionBlock) {
		return expressionBlock.getExpression().accept(this);
	}
	
	private int ArgumentsWalker(Call call, List<String> callTranslationList,
			int currentParamTarget) {
		for (int i = 0; i < call.getArguments().size(); i++) {
			List<String> paramTr = (List<String>)call.getArguments().get(i).accept(this);
			callTranslationList.addAll(paramTr);
			regUID = currentParamTarget + 1;
			currentParamTarget++;
		}
		return currentParamTarget;
	}

	public Object visit(StaticCall call) {
		return visitCall(call, null);
	}
	
	public Object visit(VirtualMethod method) {
		return methodHelper(method);
	}

	public Object visit(StaticMethod method) {
		return methodHelper(method);
	}

	public Object visit(LibraryMethod method) {
		//no need to translate library methods
		return new LinkedList<String>();
	}

	public Object visit(CallStatement callStatement) {
		return callStatement.getCall().accept(this);
	}

	public Object visit(Formal formal) {
		return null; //useless
	}

	public Object visit(Field field) {
		return null; //useless
	}
	
	public Object visit(PrimitiveType type) {
		return null; //useless
	}
	
	private void PerKindVariableHandler(VariableLocation variableLocation,
			Symbol symbol, List<String> varLocationTr,
			int targetRegisterForVariable) {
		if (symbol.getKind() == Kind.Field) {
			FieldHandler(variableLocation, symbol, varLocationTr,
					targetRegisterForVariable);
		} 
		else {
			varLocationTr.add("Move " + symbol.getLirTitle() + ",R" + targetRegisterForVariable);
		}
	}

	public Object visit(UserType type) {
		return null; //useless
	}

}